// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 索引包导出。
// 
// 索引导出数据格式是先前
// 二进制导出数据格式的演变。它的主要贡献是引入了一个
// 索引表，它允许对单个
// 声明和内联函数体进行有效的随机访问。反过来，这允许
// 避免为导入大型
// 包的编译单元进行不必要的工作。
// 
// 
// 顶级数据格式的结构为：
// 
// 头结构{
// 标记字节
// 版本uvarint 
// StringSize uvarint 
// 数据化uvarint 
// /}
// Strings[StringSize]byte 
// Data[DataSize]byte 
// 
// main index[]struct{
// PkgPath stringOff 
// PkgName stringOff 
// PkgHeight uvarint 
// 
// Name stringOff 
// Offset declOff 
// }
// }
// 
// Fingerprint[8]byte 
// 
// uvarint表示使用uvarint编码写出的uint64。
// 
// []T表示一个uvarint后面跟着那么多T对象。换句话说，
// 单词：
// 
// Len uvarint 
// Elems[Len]T 
// 
// stringOff表示在
// Strings部分内的偏移量。在这个偏移量处是另一个uvarint，后面是
// 那么多字节，它们构成了字符串值。
// 
// declOff指的是一个uvarint，表示可以在其中找到相关声明的数据
// 节内的偏移量。
// 
// 
// 有五种声明，以他们的第一个
// byte:
// 
// type Var struct{
// Tag byte/'V'
// Pos Pos 
// type typeOff 
// }
// 
// type Func struct{
// Tag byte/'F'或'G'
// Pos 
// TypeParams[]typeOff
// 签名
// /}
// 
// type Const struct{
// Tag byte
// Pos Pos 
// Value 
// }
// 
// type type struct{
// Tag byte
// Pos Pos 
// TypeParams[]typeOff
// 基础typeOff 
// 
// 方法[]结构{
// Pos Pos 
// Name stringOff 
// Recv Param 
// Signature Signature 
// }
// }
// 
// type Alias struct{
// 标记字节
// Pos Pos 
// 键入typeOff 
// /}
// 
// 
// 键入typeparam struct{
// Tag byte/'P'
// Pos-Pos-
// Implicit bool-
// Constraint typeOff-
// }
// 
// typeOff表示一个表示预声明类型、
// 或数据段偏移量的uvarint。如果uvarint小于
// predeclared，则表示索引进入predeclared 
// types列表（请参见beexport.go中的predeclared）。否则，
// 减去predeclReserved将产生类型描述符的偏移量。
// 
// 值表示类型、种类和特定类型的值。参见
// /（*exportWriter）。重视细节。
// 
// 
// 有十二种类型描述符，以itag区分：
// 
// type DefinedType struct{
// Tag itag
// Name stringOff 
// PkgPath stringOff 
// 
// 
// type PointerType struct{
// Tag itag
// Elem typeOff 
// }
// 
// type SliceType struct{
// Tag itag
// Elem typeOff 
// }
// type ArrayType struct{
// Tag itag
// Len uint64 
// Elem typeOff 
// }
// 
// type ChanType struct{
// Tag itag
// Dir uint64
// Elem typeOff 
// }
// 
// type MapType struct{
// Tag itag
// Key typeOff 
// Elem typeOff 
// }
// 
// type FuncType struct{
// Tag itag
// PkgPath stringOff 
// Signature Signature 
// }
// 
// 类型StructType struct{
// PkgPath stringOff 
// Pos Pos 
// Name stringOff 
// Type typeOff 
// Embedded bool 
// Note stringOff 
// /}
// /}
// Tag itag
// PkgPath stringOff 
// embedded s[]struct{
// Pos Pos 
// Type Type off 
// /}
// Methods[]struct{
// Pos Pos 
// Name stringOff 
// Signature Signature签名
// }
// }
// 
// /对类型参数声明的引用
// Tag itag
// Name stringOff 
// PkgPath stringOff 
// }
// /
// /泛型类型的实例化（如List[T2]或List[int]）
// /type InstanceType structure{
// Tag itag
// Pos 
// TypeArgs[]typeOff 
// /BaseType typeOff 
// /}
// 
// Tag itag
// Terms[struct{
// tilde bool 
// Type off 
// }
// }
// 
// 
// 
// Type Signature struct{
// Params[]Param 
// Variadic bool
// }
// 
// type Param struct{
// Pos Pos 
// Name stringOff 
// type typeoff 
// 编码方案。见exportWriter。
// 详细信息。
// 
// 
// 编译器特定的详细信息。
// 
// cmd/compile为内联体编写第二个索引，并且
// 在声明后附加编译器特定的其他详细信息。
// 第三方工具预计不会依赖这些细节，而
// 它们预计会变化得更快，因此被省略了
// /这里。有关详细信息，请参阅exportWriter的varExt/funcExt/etc方法。

package typecheck

import (
	"bytes"
	"crypto/md5"
	"encoding/binary"
	"fmt"
	"go/constant"
	"io"
	"math/big"
	"sort"
	"strconv"
	"strings"

	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/types"
	"cmd/internal/goobj"
	"cmd/internal/src"
)

// 当前索引导出格式版本。随着格式的变化而增加。
// 0:Go1。11编码
// 1:在Pos 
// 2:添加了泛型函数/类型的信息。非通用
// 函数/类型的导出在很大程度上保持向后兼容。突破性的更改包括：
// -将一个“kind”字节添加到常量值
const (
	iexportVersionGo1_11   = 0
	iexportVersionPosCol   = 1
	iexportVersionGenerics = 2
	iexportVersionGo1_18   = 2

	iexportVersionCurrent = 2
)

// predeclReserved是为在universe块中隐式声明的类型
// 保留的类型偏移量。
const predeclReserved = 32

// itag区分写入
// 索引导出格式的类型。
type itag uint64

const (
	// 类型
	definedType itag = iota
	pointerType
	sliceType
	arrayType
	chanType
	mapType
	signatureType
	structType
	interfaceType
	typeParamType
	instanceType // 泛型类型的实例化
	unionType
)

const (
	debug = false
	magic = 0x6742937dc293105
)

// WriteExports将索引导出格式写入out。如果扩展
// 为true，则只包括编译器扩展。
func WriteExports(out io.Writer, extensions bool) {
	if extensions {
		// 如果要导出内联实体，请调用爬虫程序标记
		// 要包含哪些实体。
		crawlExports(Target.Exports)
	}

	p := iexporter{
		allPkgs:     map[*types.Pkg]bool{},
		stringIndex: map[string]uint64{},
		declIndex:   map[*types.Sym]uint64{},
		inlineIndex: map[*types.Sym]uint64{},
		typIndex:    map[*types.Type]uint64{},
		extensions:  extensions,
	}

	for i, pt := range predeclared() {
		p.typIndex[pt] = uint64(i)
	}
	if len(p.typIndex) > predeclReserved {
		base.Fatalf("too many predeclared types: %d > %d", len(p.typIndex), predeclReserved)
	}

	// 使用导出的声明初始化工作队列。
	for _, n := range Target.Exports {
		p.pushDecl(n)
	}

	// 循环，直到不再工作。我们使用队列是因为当
	// 写出内联正文时，我们可能会发现其他需要的
	// 声明。
	for !p.declTodo.Empty() {
		p.doDecl(p.declTodo.PopLeft())
	}

	// 将索引附加到数据0部分。
	dataLen := uint64(p.data0.Len())
	w := p.newWriter()
	w.writeIndex(p.declIndex, true)
	w.writeIndex(p.inlineIndex, false)
	w.flush()

	if *base.Flag.LowerV {
		fmt.Printf("export: hdr strings %v, data %v, index %v\n", p.strings.Len(), dataLen, p.data0.Len())
	}

	// 组装收割台。
	var hdr intWriter
	hdr.WriteByte('i')
	hdr.uint64(iexportVersionCurrent)
	hdr.uint64(uint64(p.strings.Len()))
	hdr.uint64(dataLen)

	// 刷新输出。
	h := md5.New()
	wr := io.MultiWriter(out, h)
	io.Copy(wr, &hdr)
	io.Copy(wr, &p.strings)
	io.Copy(wr, &p.data0)

	// 添加指纹（由链接器对象文件使用）。
	// 将此附在末尾，这样工具（如gcimporter）就不会在意了。
	copy(base.Ctxt.Fingerprint[:], h.Sum(nil)[:])
	out.Write(base.Ctxt.Fingerprint[:])
}

// writeIndex写出一个符号索引。main index表示我们是否在写主索引，它也由非编译工具
// 读取，包括完整的包描述
// （即名称和高度）。
func (w *exportWriter) writeIndex(index map[*types.Sym]uint64, mainIndex bool) {
	// 从包到该包的符号建立一个映射。
	pkgSyms := map[*types.Pkg][]*types.Sym{}

	// 对于主索引，确保包含我们引用的每个包，即使我们没有从中导出（或重新导出）
	// 任何符号。
	if mainIndex {
		pkgSyms[types.LocalPkg] = nil
		for pkg := range w.p.allPkgs {
			pkgSyms[pkg] = nil
		}
	}

	// 按包装对符号进行分组。
	for sym := range index {
		pkgSyms[sym.Pkg] = append(pkgSyms[sym.Pkg], sym)
	}

	// 按路径对包进行排序。
	var pkgs []*types.Pkg
	for pkg := range pkgSyms {
		pkgs = append(pkgs, pkg)
	}
	sort.Slice(pkgs, func(i, j int) bool {
		return pkgs[i].Path < pkgs[j].Path
	})

	w.uint64(uint64(len(pkgs)))
	for _, pkg := range pkgs {
		w.string(pkg.Path)
		if mainIndex {
			w.string(pkg.Name)
			w.uint64(uint64(pkg.Height))
		}

		// 按名称对包中的符号进行排序。
		syms := pkgSyms[pkg]
		sort.Slice(syms, func(i, j int) bool {
			return syms[i].Name < syms[j].Name
		})

		w.uint64(uint64(len(syms)))
		for _, sym := range syms {
			w.string(sym.Name)
			w.uint64(index[sym])
		}
	}
}

type iexporter struct {
	// allPkgs跟踪所有被
	// 引用的包的导出数据，因此我们可以确保将它们包含在
	// 主索引中。
	allPkgs map[*types.Pkg]bool

	declTodo ir.NameQueue

	strings     intWriter
	stringIndex map[string]uint64

	data0       intWriter
	declIndex   map[*types.Sym]uint64
	inlineIndex map[*types.Sym]uint64
	typIndex    map[*types.Type]uint64

	extensions bool
}

// stringOff返回字符串段中s的偏移量。
// 如果尚未出现，则添加到末尾。
func (p *iexporter) stringOff(s string) uint64 {
	off, ok := p.stringIndex[s]
	if !ok {
		off = uint64(p.strings.Len())
		p.stringIndex[s] = off

		if *base.Flag.LowerV {
			fmt.Printf("export: str %v %.40q\n", off, s)
		}

		p.strings.uint64(uint64(len(s)))
		p.strings.WriteString(s)
	}
	return off
}

// pushDecl将n添加到声明工作队列（如果尚未存在）。
func (p *iexporter) pushDecl(n *ir.Name) {
	if n.Sym() == nil || n.Sym().Def != n && n.Op() != ir.OTYPE {
		base.Fatalf("weird Sym: %v, %v", n, n.Sym())
	}

	// 不要导出预声明。
	if n.Sym().Pkg == types.BuiltinPkg || n.Sym().Pkg == types.UnsafePkg {
		return
	}

	if _, ok := p.declIndex[n.Sym()]; ok {
		return
	}

	p.declIndex[n.Sym()] = ^uint64(0) // 工作队列中存在标记n 
	p.declTodo.PushRight(n)
}

// exportWriter处理写入单个数据段块的操作。
type exportWriter struct {
	p *iexporter

	data       intWriter
	currPkg    *types.Pkg
	prevFile   string
	prevLine   int64
	prevColumn int64

	// dclIndex将函数范围的声明映射到一个int，该int用于在函数的后面引用它们。对于局部变量/参数，int是
	// 非负的，并且按照Func的Dcl列表中出现的顺序排列。对于
	// 闭包变量，索引从-2开始为负。
	dclIndex           map[*ir.Name]int
	maxDclIndex        int
	maxClosureVarIndex int
}

func (p *iexporter) doDecl(n *ir.Name) {
	w := p.newWriter()
	w.setPkg(n.Sym().Pkg, false)

	switch n.Op() {
	case ir.ONAME:
		switch n.Class {
		case ir.PEXTERN:
			// 变量。
			w.tag('V')
			w.pos(n.Pos())
			w.typ(n.Type())
			if w.p.extensions {
				w.varExt(n)
			}

		case ir.PFUNC:
			if ir.IsMethod(n) {
				base.Fatalf("unexpected method: %v", n)
			}

			// 函数。
			if n.Type().TParams().NumFields() == 0 {
				w.tag('F')
			} else {
				w.tag('G')
			}
			w.pos(n.Pos())
			// 函数类型的TPRAM列表是
			// 类型参数的声明。现在就写出
			// params类型。然后，这些类型参数将被
			// 通过其类型偏移量（通过typOff）在所有
			// 签名和函数中使用的其他位置引用。
			if n.Type().TParams().NumFields() > 0 {
				w.tparamList(n.Type().TParams().FieldSlice())
			}
			w.signature(n.Type())
			if w.p.extensions {
				w.funcExt(n)
			}

		default:
			base.Fatalf("unexpected class: %v, %v", n, n.Class)
		}

	case ir.OLITERAL:
		// TODO（mdempsky）：将check扩展到所有声明。
		if n.Typecheck() == 0 {
			base.FatalfAt(n.Pos(), "missed typecheck: %v", n)
		}

		// 常数。
		w.tag('C')
		w.pos(n.Pos())
		w.value(n.Type(), n.Val())
		if w.p.extensions {
			w.constExt(n)
		}

	case ir.OTYPE:
		if n.Type().IsTypeParam() && n.Type().Underlying() == n.Type() {
			// 即使它有本地作用域，typeparam也需要通过其包和唯一名称进行
			// 声明，因为在其自身的
			// 定义期间，它可能会在其类型绑定中被引用。
			w.tag('P')
			// typeparam有一个名称，并且有一个类型绑定，而不是一个基础类型。
			w.pos(n.Pos())
			if iexportVersionCurrent >= iexportVersionGo1_18 {
				implicit := n.Type().Bound().IsImplicit()
				w.bool(implicit)
			}
			w.typ(n.Type().Bound())
			break
		}

		if n.Alias() {
			// 别名。
			w.tag('A')
			w.pos(n.Pos())
			w.typ(n.Type())
			break
		}

		// 定义的类型。
		if len(n.Type().RParams()) == 0 {
			w.tag('T')
		} else {
			w.tag('U')
		}
		w.pos(n.Pos())

		if len(n.Type().RParams()) > 0 {
			// 导出此类型所需的类型参数
			w.typeList(n.Type().RParams())
		}

		underlying := n.Type().Underlying()
		if underlying == types.ErrorType.Underlying() {
			// 对于“类型T错误”，请将错误用作
			// 基础类型，而不是错误自己的
			// 基础匿名接口。此
			// 确保与导入程序声明错误的方式一致（例如，对于预声明的对象，go/types使用nil Pkg 
			// ）。
			underlying = types.ErrorType
		}
		if underlying == types.ComparableType.Underlying() {
			// 对ComparableType和ErrorType执行相同的操作。
			underlying = types.ComparableType
		}
		if base.Flag.G > 0 && underlying == types.AnyType.Underlying() {
			// 对AnyType和ErrorType执行相同的操作。
			underlying = types.AnyType
		}
		w.typ(underlying)

		t := n.Type()
		if t.IsInterface() {
			if w.p.extensions {
				w.typeExt(t)
			}
			break
		}

		// 排序方法，与类型2保持一致。
		methods := append([]*types.Field(nil), t.Methods().Slice()...)
		if base.Debug.UnifiedQuirks != 0 {
			sort.Sort(types.MethodsByName(methods))
		}

		w.uint64(uint64(len(methods)))
		for _, m := range methods {
			w.pos(m.Pos)
			w.selector(m.Sym)
			w.param(m.Type.Recv())
			w.signature(m.Type)
		}

		if w.p.extensions {
			w.typeExt(t)
			for _, m := range methods {
				w.methExt(m)
			}
		}

	default:
		base.Fatalf("unexpected node: %v", n)
	}

	w.finish("dcl", p.declIndex, n.Sym())
}

func (w *exportWriter) tag(tag byte) {
	w.data.WriteByte(tag)
}

func (w *exportWriter) finish(what string, index map[*types.Sym]uint64, sym *types.Sym) {
	off := w.flush()
	if *base.Flag.LowerV {
		fmt.Printf("export: %v %v %v\n", what, off, sym)
	}
	index[sym] = off
}

func (p *iexporter) doInline(f *ir.Name) {
	w := p.newWriter()
	w.setPkg(fnpkg(f), false)

	w.dclIndex = make(map[*ir.Name]int, len(f.Func.Inl.Dcl))
	w.funcBody(f.Func)

	w.finish("inl", p.inlineIndex, f.Sym())
}

func (w *exportWriter) pos(pos src.XPos) {
	p := base.Ctxt.PosTable.Pos(pos)
	file := p.Base().AbsFilename()
	line := int64(p.RelLine())
	column := int64(p.RelCol())

	// 编码相对于最后一个位置的位置：列
	// 增量，然后是行增量，然后是文件名。我们保留列和行增量的
	// 底部位，用于编码剩余字段是否存在。
	// 
	// 注意：由于数据对象可能会被无序读取（或者根本不会被读取），我们只能在单个
	// 对象中应用增量编码。这是通过跟踪prevFile、
	// prevLine和prevColumn作为exportWriter的字段来隐式处理的。

	deltaColumn := (column - w.prevColumn) << 1
	deltaLine := (line - w.prevLine) << 1

	if file != w.prevFile {
		deltaLine |= 1
	}
	if deltaLine != 0 {
		deltaColumn |= 1
	}

	w.int64(deltaColumn)
	if deltaColumn&1 != 0 {
		w.int64(deltaLine)
		if deltaLine&1 != 0 {
			w.string(file)
		}
	}

	w.prevFile = file
	w.prevLine = line
	w.prevColumn = column
}

func (w *exportWriter) pkg(pkg *types.Pkg) {
	// TODO（mdempsky）：向类型添加标志。Pkg标记伪包。
	if pkg == ir.Pkgs.Go {
		base.Fatalf("export of pseudo-package: %q", pkg.Path)
	}

	// 确保在主索引中声明所有引用的包。
	w.p.allPkgs[pkg] = true

	w.string(pkg.Path)
}

func (w *exportWriter) qualifiedIdent(n *ir.Name) {
	// 确保所有引用的声明也被写出来。
	w.p.pushDecl(n)

	s := n.Sym()
	w.string(s.Name)
	w.pkg(s.Pkg)
}

const blankMarker = "$"

// TparamExportName使用指定的唯一前缀和类型param的索引，为方法中的类型param或泛型
// type创建唯一名称。如果类型PARAM是空的，则只使用索引
// 导出/导入中进行替换时，需要一个唯一的名称，以便将空类型参数与正确的约束关联起来。
func TparamExportName(prefix string, name string, index int) string {
	if name == "_" {
		name = blankMarker + strconv.Itoa(index)
	}
	return prefix + "." + name
}

// TparamName在去掉类型参数的
// 限定前缀并恢复空名称编码后，返回类型参数的真实名称。有关详细信息，请参阅TparamExportName 
// 。
func TparamName(exportName string) string {
	// 从类型参数名称中删除使其唯一的“路径”。
	ix := strings.LastIndex(exportName, ".")
	if ix < 0 {
		return ""
	}
	name := exportName[ix+1:]
	if strings.HasPrefix(name, blankMarker) {
		return "_"
	}
	return name
}

func (w *exportWriter) selector(s *types.Sym) {
	if w.currPkg == nil {
		base.Fatalf("missing currPkg")
	}

	// 如果正在写入的选择器未报告，则它会附带一个包限定符。
	// 如果正在写入的选择器已导出，则该选择器不符合软件包条件。
	// 请参见规范：https:
	// 作为一种优化，我们并不是每次都编写包，而是在一组选择器之前调用setPkg（所有选择器必须具有相同的包限定符）。
	pkg := w.currPkg
	if types.IsExported(s.Name) {
		pkg = types.LocalPkg
	}
	if s.Pkg != pkg {
		base.Fatalf("package mismatch in selector: %v in package %q, but want %q", s, s.Pkg.Path, pkg.Path)
	}

	w.string(s.Name)
}

func (w *exportWriter) typ(t *types.Type) {
	w.data.uint64(w.p.typOff(t))
}

// 与上述标准编码函数相比，本节中的“外来”函数编码的
// 项目范围更广。这些类型包括
// 不出现在声明中，只出现在代码中的类型，例如
// 方法类型。这些方法需要与标准
// 编码函数分开，因为我们不想修改标准函数生成的编码
// 因为导出的
// 信息是由编译器之外的工具读取的。

// exoticType将类型导出到编写器。
func (w *exportWriter) exoticType(t *types.Type) {
	switch {
	case t == nil:
		// 作为语句的调用没有类型。
		w.data.uint64(exoticTypeNil)
	case t.IsStruct() && t.StructType().Funarg != types.FunargNone:
		// 这些是奇怪的结构，用于表示由多个返回函数返回的类型的元组。
		// 它们不适合标准结构型模具。例如，
		// 他们没有任何包裹信息。
		w.data.uint64(exoticTypeTuple)
		w.uint64(uint64(t.StructType().Funarg))
		w.uint64(uint64(t.NumFields()))
		for _, f := range t.FieldSlice() {
			w.pos(f.Pos)
			s := f.Sym
			if s == nil {
				w.uint64(0)
			} else if s.Pkg == nil {
				w.uint64(exoticTypeSymNoPkg)
				w.string(s.Name)
			} else {
				w.uint64(exoticTypeSymWithPkg)
				w.pkg(s.Pkg)
				w.string(s.Name)
			}
			w.typ(f.Type)
			if f.Embedded != 0 || f.Note != "" {
				panic("extra info in funarg struct field")
			}
		}
	case t.Kind() == types.TFUNC && t.Recv() != nil:
		w.data.uint64(exoticTypeRecv)
		// 接口方法类型具有假接收器类型。
		isFakeRecv := t.Recv().Type == types.FakeRecvType()
		w.bool(isFakeRecv)
		if !isFakeRecv {
			w.exoticParam(t.Recv())
		}
		w.exoticSignature(t)

	default:
		// 一个常规类型。
		w.data.uint64(exoticTypeRegular)
		w.typ(t)
	}
}

const (
	exoticTypeNil = iota
	exoticTypeTuple
	exoticTypeRecv
	exoticTypeRegular
)
const (
	exoticTypeSymNil = iota
	exoticTypeSymNoPkg
	exoticTypeSymWithPkg
)

// 导出一个选择器，但其包可能与正在编译的包不匹配。这是一个单独的函数
// 因为go/types读取器固定了标准的selector（）序列化格式
// 。这个只能在
// 内联/通用体导出期间使用。
func (w *exportWriter) exoticSelector(s *types.Sym) {
	pkg := w.currPkg
	if types.IsExported(s.Name) {
		pkg = types.LocalPkg
	}

	w.string(s.Name)
	if s.Pkg == pkg {
		w.uint64(0)
	} else {
		w.uint64(1)
		w.pkg(s.Pkg)
	}
}

func (w *exportWriter) exoticSignature(t *types.Type) {
	hasPkg := t.Pkg() != nil
	w.bool(hasPkg)
	if hasPkg {
		w.pkg(t.Pkg())
	}
	w.exoticParamList(t.Params().FieldSlice())
	w.exoticParamList(t.Results().FieldSlice())
}

func (w *exportWriter) exoticParamList(fs []*types.Field) {
	w.uint64(uint64(len(fs)))
	for _, f := range fs {
		w.exoticParam(f)
	}

}
func (w *exportWriter) exoticParam(f *types.Field) {
	w.pos(f.Pos)
	w.exoticSym(f.Sym)
	w.uint64(uint64(f.Offset))
	w.exoticType(f.Type)
	w.bool(f.IsDDD())
}

func (w *exportWriter) exoticField(f *types.Field) {
	w.pos(f.Pos)
	w.exoticSym(f.Sym)
	w.uint64(uint64(f.Offset))
	w.exoticType(f.Type)
	w.string(f.Note)
}

func (w *exportWriter) exoticSym(s *types.Sym) {
	if s == nil {
		w.string("")
		return
	}
	if s.Name == "" {
		base.Fatalf("empty symbol name")
	}
	w.string(s.Name)
	if !types.IsExported(s.Name) {
		w.pkg(s.Pkg)
	}
}

func (p *iexporter) newWriter() *exportWriter {
	return &exportWriter{p: p}
}

func (w *exportWriter) flush() uint64 {
	off := uint64(w.p.data0.Len())
	io.Copy(&w.p.data0, &w.data)
	return off
}

func (p *iexporter) typOff(t *types.Type) uint64 {
	off, ok := p.typIndex[t]
	if !ok {
		w := p.newWriter()
		w.doTyp(t)
		rawOff := w.flush()
		if *base.Flag.LowerV {
			fmt.Printf("export: typ %v %v\n", rawOff, t)
		}
		off = predeclReserved + rawOff
		p.typIndex[t] = off
	}
	return off
}

func (w *exportWriter) startType(k itag) {
	w.data.uint64(uint64(k))
}

func (w *exportWriter) doTyp(t *types.Type) {
	s := t.Sym()
	if s != nil && t.OrigType() != nil {
		assert(base.Flag.G > 0)
		// 这是一个实例化类型——可以是像
		// Value[T2]这样的重新实例化，也可以是像Value[int]这样的完整实例化。
		if strings.Index(s.Name, "[") < 0 {
			base.Fatalf("incorrect name for instantiated type")
		}
		w.startType(instanceType)
		w.pos(t.Pos())
		// 导出实例化类型的类型参数。
		// 实例化的类型可以在方法头中（例如，“func（v 
		// /*值[T2]）集合（…）{ ... }），因此类型参数是“新的”
		// typeparams。或者实例化的类型可以在
		// 函数/方法体中，因此类型参数是具体的
		// 类型或函数/方法头中的现有typeparams。
		w.typeList(t.RParams())
		// 导出对基类型的引用。
		baseType := t.OrigType()
		w.typ(baseType)
		return
	}

	// 基础（）==t'检查是为了确认这是一个基本类型参数
	// 类型，而不是一个定义了类型参数基础类型的类型，比如：
	// type orderedAbs[t any]t 
	if t.IsTypeParam() && t.Underlying() == t {
		assert(base.Flag.G > 0)
		if s.Pkg == types.BuiltinPkg || s.Pkg == types.UnsafePkg {
			base.Fatalf("builtin type missing from typIndex: %v", t)
		}
		// 写出第一次使用类型参数作为限定标识。
		// 这将强制执行param类型的“声明”。
		w.startType(typeParamType)
		w.qualifiedIdent(t.Obj().(*ir.Name))
		return
	}

	if s != nil {
		if s.Pkg == types.BuiltinPkg || s.Pkg == types.UnsafePkg {
			base.Fatalf("builtin type missing from typIndex: %v", t)
		}

		w.startType(definedType)
		w.qualifiedIdent(t.Obj().(*ir.Name))
		return
	}

	switch t.Kind() {
	case types.TPTR:
		w.startType(pointerType)
		w.typ(t.Elem())

	case types.TSLICE:
		w.startType(sliceType)
		w.typ(t.Elem())

	case types.TARRAY:
		w.startType(arrayType)
		w.uint64(uint64(t.NumElem()))
		w.typ(t.Elem())

	case types.TCHAN:
		w.startType(chanType)
		w.uint64(uint64(t.ChanDir()))
		w.typ(t.Elem())

	case types.TMAP:
		w.startType(mapType)
		w.typ(t.Key())
		w.typ(t.Elem())

	case types.TFUNC:
		w.startType(signatureType)
		w.setPkg(t.Pkg(), true)
		w.signature(t)

	case types.TSTRUCT:
		w.startType(structType)
		w.setPkg(t.Pkg(), true)

		w.uint64(uint64(t.NumFields()))
		for _, f := range t.FieldSlice() {
			w.pos(f.Pos)
			w.selector(f.Sym)
			w.typ(f.Type)
			w.bool(f.Embedded != 0)
			w.string(f.Note)
		}

	case types.TINTER:
		var embeddeds, methods []*types.Field
		for _, m := range t.Methods().Slice() {
			if m.Sym != nil {
				methods = append(methods, m)
			} else {
				embeddeds = append(embeddeds, m)
			}
		}

		// 排序方法和嵌入类型，以与类型2保持一致。
		// 注意：嵌入类型可能是匿名的，types2使用sort对其进行排序。也很稳定。
		if base.Debug.UnifiedQuirks != 0 {
			sort.Sort(types.MethodsByName(methods))
			sort.Stable(types.EmbeddedsByName(embeddeds))
		}

		w.startType(interfaceType)
		w.setPkg(t.Pkg(), true)

		w.uint64(uint64(len(embeddeds)))
		for _, f := range embeddeds {
			w.pos(f.Pos)
			w.typ(f.Type)
		}

		w.uint64(uint64(len(methods)))
		for _, f := range methods {
			w.pos(f.Pos)
			w.selector(f.Sym)
			w.signature(f.Type)
		}

	case types.TUNION:
		assert(base.Flag.G > 0)
		// TODO（danscales）：可能会在更多
		// 紧凑型。
		w.startType(unionType)
		nt := t.NumTerms()
		w.uint64(uint64(nt))
		for i := 0; i < nt; i++ {
			typ, tilde := t.Term(i)
			w.bool(tilde)
			w.typ(typ)
		}

	default:
		base.Fatalf("unexpected type: %v", t)
	}
}

func (w *exportWriter) setPkg(pkg *types.Pkg, write bool) {
	if pkg == types.NoPkg {
		base.Fatalf("missing pkg")
	}

	if write {
		w.pkg(pkg)
	}

	w.currPkg = pkg
}

func (w *exportWriter) signature(t *types.Type) {
	w.paramList(t.Params().FieldSlice())
	w.paramList(t.Results().FieldSlice())
	if n := t.Params().NumFields(); n > 0 {
		w.bool(t.Params().Field(n - 1).IsDDD())
	}
}

func (w *exportWriter) typeList(ts []*types.Type) {
	w.uint64(uint64(len(ts)))
	for _, rparam := range ts {
		w.typ(rparam)
	}
}

func (w *exportWriter) tparamList(fs []*types.Field) {
	w.uint64(uint64(len(fs)))
	for _, f := range fs {
		if !f.Type.IsTypeParam() {
			base.Fatalf("unexpected non-typeparam")
		}
		w.typ(f.Type)
	}
}

func (w *exportWriter) paramList(fs []*types.Field) {
	w.uint64(uint64(len(fs)))
	for _, f := range fs {
		w.param(f)
	}
}

func (w *exportWriter) param(f *types.Field) {
	w.pos(f.Pos)
	w.localIdent(types.OrigSym(f.Sym))
	w.typ(f.Type)
}

func constTypeOf(typ *types.Type) constant.Kind {
	switch typ {
	case types.UntypedInt, types.UntypedRune:
		return constant.Int
	case types.UntypedFloat:
		return constant.Float
	case types.UntypedComplex:
		return constant.Complex
	}

	switch typ.Kind() {
	case types.TBOOL:
		return constant.Bool
	case types.TSTRING:
		return constant.String
	case types.TINT, types.TINT8, types.TINT16, types.TINT32, types.TINT64,
		types.TUINT, types.TUINT8, types.TUINT16, types.TUINT32, types.TUINT64, types.TUINTPTR:
		return constant.Int
	case types.TFLOAT32, types.TFLOAT64:
		return constant.Float
	case types.TCOMPLEX64, types.TCOMPLEX128:
		return constant.Complex
	}

	base.Fatalf("unexpected constant type: %v", typ)
	return 0
}

func (w *exportWriter) value(typ *types.Type, v constant.Value) {
	w.typ(typ)

	if iexportVersionCurrent >= iexportVersionGo1_18 {
		w.int64(int64(v.Kind()))
	}

	var kind constant.Kind
	var valType *types.Type

	if typ.IsTypeParam() {
		kind = v.Kind()
		if iexportVersionCurrent < iexportVersionGo1_18 {
			// 如果常量出现在必须匹配该类型参数类型的位置（例如，在二进制
			// 操作中使用该类型的变量），则该常量将具有TYPEPARAM类型。如果是这样，那么
			// 我们也必须写出它的实际常量种类，这样在导入期间可以正确地读入它的
			// 常量val。
			w.int64(int64(kind))
		}

		switch kind {
		case constant.Int:
			valType = types.Types[types.TINT64]
		case constant.Float:
			valType = types.Types[types.TFLOAT64]
		case constant.Complex:
			valType = types.Types[types.TCOMPLEX128]
		}
	} else {
		ir.AssertValidTypeForConst(typ, v)
		kind = constTypeOf(typ)
		valType = typ
	}

	// 每个类型只有一个可接受的常量表示，因此我们可以在这里直接在v.Kind（）上切换类型。然而，打开类型
	// （在非typeparam的情况下）增加了导入逻辑的对称性，
	// 提供了有用的一致性检查。

	switch kind {
	case constant.Bool:
		w.bool(constant.BoolVal(v))
	case constant.String:
		w.string(constant.StringVal(v))
	case constant.Int:
		w.mpint(v, valType)
	case constant.Float:
		w.mpfloat(v, valType)
	case constant.Complex:
		w.mpfloat(constant.Real(v), valType)
		w.mpfloat(constant.Imag(v), valType)
	}
}

func intSize(typ *types.Type) (signed bool, maxBytes uint) {
	if typ.IsUntyped() {
		return true, ir.ConstPrec / 8
	}

	switch typ.Kind() {
	case types.TFLOAT32, types.TCOMPLEX64:
		return true, 3
	case types.TFLOAT64, types.TCOMPLEX128:
		return true, 7
	}

	signed = typ.IsSigned()
	maxBytes = uint(typ.Size())

	// go/types API不向进口商公开大小，所以他们不知道这些类型有多大。
	switch typ.Kind() {
	case types.TINT, types.TUINT, types.TUINTPTR:
		maxBytes = 8
	}

	return
}

// mpint导出多精度整数。
// 
// 对于无符号类型，小值作为单个
// 字节写入。较大的值被写为带有长度前缀的big-endian 
// 字节字符串，其中长度前缀被编码为其补码。
// 例如，字节0、1和2直接表示整数
// 值0、1和2；而字节255、254和253表示后跟1-、
// 2-和3字节的大端字符串。
// 
// 有符号类型的编码使用与
// 无符号类型相同的一般方法，但小值使用之字形编码，大值的长度前缀字节的底部位保留为
// 符号位。
// 
// 大小编码之间的确切界限根据编码类型的值
// 所需的最大字节数而变化。作为一种特殊情况，8位类型始终被编码为
// 单字节。
func (w *exportWriter) mpint(x constant.Value, typ *types.Type) {
	signed, maxBytes := intSize(typ)

	negative := constant.Sign(x) < 0
	if !signed && negative {
		base.Fatalf("negative unsigned integer; type %v, value %v", typ, x)
	}

	b := constant.Bytes(x) // 小端点
	for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
		b[i], b[j] = b[j], b[i]
	}

	if len(b) > 0 && b[0] == 0 {
		base.Fatalf("leading zeros")
	}
	if uint(len(b)) > maxBytes {
		base.Fatalf("bad mpint length: %d > %d (type %v, value %v)", len(b), maxBytes, typ, x)
	}

	maxSmall := 256 - maxBytes
	if signed {
		maxSmall = 256 - 2*maxBytes
	}
	if maxBytes == 1 {
		maxSmall = 256
	}

	// 检查x是否可以使用小值编码。
	if len(b) <= 1 {
		var ux uint
		if len(b) == 1 {
			ux = uint(b[0])
		}
		if signed {
			ux <<= 1
			if negative {
				ux--
			}
		}
		if ux < maxSmall {
			w.data.WriteByte(byte(ux))
			return
		}
	}

	n := 256 - uint(len(b))
	if signed {
		n = 256 - 2*uint(len(b))
		if negative {
			n |= 1
		}
	}
	if n < maxSmall || n >= 256 {
		base.Fatalf("encoding mistake: %d, %v, %v => %d", len(b), signed, negative, n)
	}

	w.data.WriteByte(byte(n))
	w.data.Write(b)
}

// mpfloat导出多精度浮点数。
// 
// 将数字的值分解为尾数×2**指数，其中
// 尾数为整数。值被写为尾数（作为
// 多精度整数），然后是指数，如果尾数为零，则指数被忽略。
func (w *exportWriter) mpfloat(v constant.Value, typ *types.Type) {
	f := ir.BigFloat(v)
	if f.IsInf() {
		base.Fatalf("infinite constant")
	}

	// 分解为f=mant×2**exp，0.5<=mant<1。
	var mant big.Float
	exp := int64(f.MantExp(&mant))

	// 缩放，使mant为整数。
	prec := mant.MinPrec()
	mant.SetMantExp(&mant, int(prec))
	exp -= int64(prec)

	manti, acc := mant.Int(nil)
	if acc != big.Exact {
		base.Fatalf("mantissa scaling failed for %f (%s)", f, acc)
	}
	w.mpint(constant.Make(manti), typ)
	if manti.Sign() != 0 {
		w.int64(exp)
	}
}

func (w *exportWriter) mprat(v constant.Value) {
	r, ok := constant.Val(v).(*big.Rat)
	if !w.bool(ok) {
		return
	}
	// TODO（mdempsky）：在将IEExportVersion公开给
	// 编码。
	// gcimporter之前，提出一种更高效的二进制
	w.string(r.String())
}

func (w *exportWriter) bool(b bool) bool {
	var x uint64
	if b {
		x = 1
	}
	w.uint64(x)
	return b
}

func (w *exportWriter) int64(x int64)   { w.data.int64(x) }
func (w *exportWriter) uint64(x uint64) { w.data.uint64(x) }
func (w *exportWriter) string(s string) { w.uint64(w.p.stringOff(s)) }

// 特定于编译器的扩展。

func (w *exportWriter) constExt(n *ir.Name) {
	// 在内部，我们现在使用
	// 常量，但
	// go/constant以无限精度有理数表示非类型浮点和复杂
	// gcimporter已知的“公共”导出数据格式只支持512位浮点常量。
	// 万一有理数被证明是个坏主意，我们想把
	// 切换回固定精度常数，现在我们
	// 继续在公共
	// 数据部分写出512位的截断，并在
	// 编译器的扩展数据中写出精确的有理常数。此外，我们只需要担心导出声明常量的有理数，因为表达式中出现的常量将已经强制使用具体的固定精度类型。
	// 
	// 最后，假设我们坚持使用理性，我们
	// 应该使用iexportVersion来支持理性，并完成整个GCIMPLORT更新歌舞。
	// 
	// TODO（mdempsky）：准备好这方面的人声。

	switch n.Type() {
	case types.UntypedFloat:
		w.mprat(n.Val())
	case types.UntypedComplex:
		v := n.Val()
		w.mprat(constant.Real(v))
		w.mprat(constant.Imag(v))
	}
}

func (w *exportWriter) varExt(n *ir.Name) {
	w.linkname(n.Sym())
	w.symIdx(n.Sym())
}

func (w *exportWriter) funcExt(n *ir.Name) {
	w.linkname(n.Sym())
	w.symIdx(n.Sym())

	// 记录定义ABI，以便跨ABI调用可以直接进行。
	// 这对于调用某些
	// 在汇编中实现的常见函数（例如bytealg）的性能非常重要。
	w.uint64(uint64(n.Func.ABI))

	w.uint64(uint64(n.Func.Pragma))

	// 逃逸分析。
	for _, fs := range &types.RecvsParams {
		for _, f := range fs(n.Type()).FieldSlice() {
			w.string(f.Note)
		}
	}

	// 写出内联体或泛型函数/方法的体。
	if n.Type().HasTParam() && n.Func.Body != nil && n.Func.Inl == nil {
		base.FatalfAt(n.Pos(), "generic function is not marked inlineable")
	}
	if n.Func.Inl != nil {
		w.uint64(1 + uint64(n.Func.Inl.Cost))
		w.bool(n.Func.Inl.CanDelayResults)
		if n.Func.ExportInline() || n.Type().HasTParam() {
			if n.Type().HasTParam() {
				// 如果此泛型函数/方法来自另一个
				// 包，但我们没有在
				// 此包中用于实例化，我们可能还没有导入它。
				ImportedBody(n.Func)
			}
			w.p.doInline(n)
		}

		// 内联函数的Endlineno。
		w.pos(n.Func.Endlineno)
	} else {
		w.uint64(0)
	}
}

func (w *exportWriter) methExt(m *types.Field) {
	w.bool(m.Nointerface())
	w.funcExt(m.Nname.(*ir.Name))
}

func (w *exportWriter) linkname(s *types.Sym) {
	w.string(s.Linkname)
}

func (w *exportWriter) symIdx(s *types.Sym) {
	lsym := s.Linksym()
	if lsym.PkgIdx > goobj.PkgIdxSelf || (lsym.PkgIdx == goobj.PkgIdxInvalid && !lsym.Indexed()) || s.Linkname != "" {
		// 不导出非包装符号、链接名称符号、
		// 和没有索引的符号。它们只能由
		// 名称引用。
		w.int64(-1)
	} else {
		// 对于已定义的符号，请导出其索引。
		// 要重新导出导入的符号，请传递其索引。
		w.int64(int64(lsym.SymIdx))
	}
}

func (w *exportWriter) typeExt(t *types.Type) {
	// 导出此类型是否标记为notinheap。
	w.bool(t.NotInHeap())
	// 对于类型T，导出T和*T的类型描述符符号索引。
	if i, ok := typeSymIdx[t]; ok {
		w.int64(i[0])
		w.int64(i[1])
		return
	}
	w.symIdx(types.TypeSym(t))
	w.symIdx(types.TypeSym(t.PtrTo()))
}

// 内联体。

func (w *exportWriter) writeNames(dcl []*ir.Name) {
	w.int64(int64(len(dcl)))
	for i, n := range dcl {
		w.pos(n.Pos())
		w.localIdent(n.Sym())
		w.typ(n.Type())
		w.dclIndex[n] = w.maxDclIndex + i
	}
	w.maxDclIndex += len(dcl)
}

func (w *exportWriter) funcBody(fn *ir.Func) {
	// fmt。Printf（“导出%s\n”，fn.Nname.Sym（）。名称）
	w.writeNames(fn.Inl.Dcl)

	w.stmtList(fn.Inl.Body)
}

func (w *exportWriter) stmtList(list []ir.Node) {
	for _, n := range list {
		w.node(n)
	}
	w.op(ir.OEND)
}

func (w *exportWriter) node(n ir.Node) {
	if ir.OpPrec[n.Op()] < 0 {
		w.stmt(n)
	} else {
		w.expr(n)
	}
}

func isNonEmptyAssign(n ir.Node) bool {
	switch n.Op() {
	case ir.OAS:
		if n.(*ir.AssignStmt).Y != nil {
			return true
		}
	case ir.OAS2, ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV:
		return true
	}
	return false
}

// 注意：对于具有
// 非空n.Ninit且其中n不是非空赋值或具有自然init 
// 节（例如在“if”、“for”等）的语句节点n，stmt将发出多个节点。
func (w *exportWriter) stmt(n ir.Node) {
	if len(n.Init()) > 0 && !ir.StmtWithInit(n.Op()) && !isNonEmptyAssign(n) && n.Op() != ir.ORANGE {
		// 无法在此处使用stmtList，因为我们不希望最终OEND 
		for _, n := range n.Init() {
			w.stmt(n)
		}
	}

	switch n.Op() {
	case ir.OBLOCK:
		// 导出数据中没有对象锁。
		// 将内容内联到这个语句列表中，
		// 就像上面的init列表一样。
		// （目前，解析器和类型检查器都不会生成对象块节点，除非表示空的
		// 函数体，尽管这可能会改变。）
		n := n.(*ir.BlockStmt)
		for _, n := range n.List {
			w.stmt(n)
		}

	case ir.ODCL:
		n := n.(*ir.Decl)
		if ir.IsBlank(n.X) {
			return // 空白声明对进口商不有用
		}
		w.op(ir.ODCL)
		w.localName(n.X)

	case ir.OAS:
		// 不要导出“v=<N>”初始化语句，希望它们始终是
		// 前面的DCL将被重新解析并进行类型检查以再次复制
		// v=<N>。
		n := n.(*ir.AssignStmt)
		if n.Y != nil {
			w.op(ir.OAS)
			w.pos(n.Pos())
			w.stmtList(n.Init())
			w.expr(n.X)
			w.expr(n.Y)
			w.bool(n.Def)
		}

	case ir.OASOP:
		n := n.(*ir.AssignOpStmt)
		w.op(ir.OASOP)
		w.pos(n.Pos())
		w.op(n.AsOp)
		w.expr(n.X)
		if w.bool(!n.IncDec) {
			w.expr(n.Y)
		}

	case ir.OAS2, ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV:
		n := n.(*ir.AssignListStmt)
		if go117ExportTypes {
			w.op(n.Op())
		} else {
			w.op(ir.OAS2)
		}
		w.pos(n.Pos())
		w.stmtList(n.Init())
		w.exprList(n.Lhs)
		w.exprList(n.Rhs)
		w.bool(n.Def)

	case ir.ORETURN:
		n := n.(*ir.ReturnStmt)
		w.op(ir.ORETURN)
		w.pos(n.Pos())
		w.exprList(n.Results)

	// case-ORETJMP:
	// 不可访问-由蹦床例程编译器生成

	case ir.OGO, ir.ODEFER:
		n := n.(*ir.GoDeferStmt)
		w.op(n.Op())
		w.pos(n.Pos())
		w.expr(n.Call)

	case ir.OIF:
		n := n.(*ir.IfStmt)
		w.op(ir.OIF)
		w.pos(n.Pos())
		w.stmtList(n.Init())
		w.expr(n.Cond)
		w.stmtList(n.Body)
		w.stmtList(n.Else)

	case ir.OFOR:
		n := n.(*ir.ForStmt)
		w.op(ir.OFOR)
		w.pos(n.Pos())
		w.stmtList(n.Init())
		w.exprsOrNil(n.Cond, n.Post)
		w.stmtList(n.Body)

	case ir.ORANGE:
		n := n.(*ir.RangeStmt)
		w.op(ir.ORANGE)
		w.pos(n.Pos())
		w.stmtList(n.Init())
		w.exprsOrNil(n.Key, n.Value)
		w.expr(n.X)
		w.stmtList(n.Body)

	case ir.OSELECT:
		n := n.(*ir.SelectStmt)
		w.op(n.Op())
		w.pos(n.Pos())
		w.stmtList(n.Init())
		w.commList(n.Cases)

	case ir.OSWITCH:
		n := n.(*ir.SwitchStmt)
		w.op(n.Op())
		w.pos(n.Pos())
		w.stmtList(n.Init())
		w.exprsOrNil(n.Tag, nil)
		w.caseList(n.Cases, isNamedTypeSwitch(n.Tag))

	// case-OCASE:
	// 由case-list处理

	case ir.OFALL:
		n := n.(*ir.BranchStmt)
		w.op(ir.OFALL)
		w.pos(n.Pos())

	case ir.OBREAK, ir.OCONTINUE, ir.OGOTO, ir.OLABEL:
		w.op(n.Op())
		w.pos(n.Pos())
		label := ""
		if sym := n.Sym(); sym != nil {
			label = sym.Name
		}
		w.string(label)

	default:
		base.Fatalf("exporter: CANNOT EXPORT: %v\nPlease notify gri@\n", n.Op())
	}
}

func isNamedTypeSwitch(x ir.Node) bool {
	guard, ok := x.(*ir.TypeSwitchGuard)
	return ok && guard.Tag != nil
}

func (w *exportWriter) caseList(cases []*ir.CaseClause, namedTypeSwitch bool) {
	w.uint64(uint64(len(cases)))
	for _, cas := range cases {
		w.pos(cas.Pos())
		w.stmtList(cas.List)
		if namedTypeSwitch {
			w.localName(cas.Var)
		}
		w.stmtList(cas.Body)
	}
}

func (w *exportWriter) commList(cases []*ir.CommClause) {
	w.uint64(uint64(len(cases)))
	for _, cas := range cases {
		w.pos(cas.Pos())
		defaultCase := cas.Comm == nil
		w.bool(defaultCase)
		if !defaultCase {
			// 仅为非默认原因调用w.node（cas.Comm非零）
			w.node(cas.Comm)
		}
		w.stmtList(cas.Body)
	}
}

func (w *exportWriter) exprList(list ir.Nodes) {
	for _, n := range list {
		w.expr(n)
	}
	w.op(ir.OEND)
}

func simplifyForExport(n ir.Node) ir.Node {
	switch n.Op() {
	case ir.OPAREN:
		n := n.(*ir.ParenExpr)
		return simplifyForExport(n.X)
	}
	return n
}

func (w *exportWriter) expr(n ir.Node) {
	n = simplifyForExport(n)
	switch n.Op() {
	// 表达式
	case ir.ONIL:
		n := n.(*ir.NilExpr)
		// 如果n是一个typeparam，它将已经被types2 typechecker检查过
		// 是否正确使用。
		if !n.Type().IsTypeParam() && !n.Type().HasNil() {
			base.Fatalf("unexpected type for nil: %v", n.Type())
		}
		w.op(ir.ONIL)
		w.pos(n.Pos())
		w.typ(n.Type())

	case ir.OLITERAL:
		w.op(ir.OLITERAL)
		if ir.HasUniquePos(n) {
			w.pos(n.Pos())
		} else {
			w.pos(src.NoXPos)
		}
		w.value(n.Type(), n.Val())

	case ir.ONAME:
		// 包作用域名称。
		n := n.(*ir.Name)
		if (n.Class == ir.PEXTERN || n.Class == ir.PFUNC) && !ir.IsBlank(n) {
			w.op(ir.ONONAME)
			// 表明这不是一个好的条目。
			w.bool(false)
			w.qualifiedIdent(n)
			if go117ExportTypes {
				w.typ(n.Type())
			}
			break
		}

		// 函数作用域名称。
		// 我们这里不需要类型，因为类型将在
		// n.
		w.op(ir.ONAME)

		// 声明中提供。这将处理我们尚未将调用
		// 转换为内置名的情况，因此我们必须在
		// 内置名包中写出内置名。
		isBuiltin := n.BuiltinOp != ir.OXXX
		w.bool(isBuiltin)
		if isBuiltin {
			w.bool(n.Sym().Pkg == types.UnsafePkg)
			w.string(n.Sym().Name)
			break
		}
		w.localName(n)

	case ir.ONONAME:
		w.op(ir.ONONAME)
		// 这只能用于泛型函数中的OKEY节点。将其标记为
		// 作为密钥条目。
		w.bool(true)
		s := n.Sym()
		w.string(s.Name)
		w.pkg(s.Pkg)
		if go117ExportTypes {
			w.typ(n.Type())
		}

	// case OPACK:
	// 应该通过类型检查解决-默认情况下处理

	case ir.OTYPE:
		w.op(ir.OTYPE)
		w.typ(n.Type())

	case ir.ODYNAMICTYPE:
		n := n.(*ir.DynamicType)
		w.op(ir.ODYNAMICTYPE)
		w.pos(n.Pos())
		w.expr(n.X)
		if n.ITab != nil {
			w.bool(true)
			w.expr(n.ITab)
		} else {
			w.bool(false)
		}
		w.typ(n.Type())

	case ir.OTYPESW:
		n := n.(*ir.TypeSwitchGuard)
		w.op(ir.OTYPESW)
		w.pos(n.Pos())
		var s *types.Sym
		if n.Tag != nil {
			if n.Tag.Op() != ir.ONONAME {
				base.Fatalf("expected ONONAME, got %v", n.Tag)
			}
			s = n.Tag.Sym()
		}
		w.localIdent(s) // 声明的伪变量，如果有
		w.expr(n.X)

	// case OTARRAY、OTMAP、OTCHAN、OTSTRUCT、OTINTER、，OTFUNC:
	// 应该通过类型检查来解决-默认情况下处理

	case ir.OCLOSURE:
		n := n.(*ir.ClosureExpr)
		w.op(ir.OCLOSURE)
		w.pos(n.Pos())
		old := w.currPkg
		w.setPkg(n.Type().Pkg(), true)
		w.signature(n.Type())
		w.setPkg(old, true)

		// 写出每个条件变量外部的id。此闭包的
		// 条件变量本身将在导入期间重新创建
		// 。
		w.int64(int64(len(n.Func.ClosureVars)))
		for i, cv := range n.Func.ClosureVars {
			w.pos(cv.Pos())
			w.localName(cv.Outer)
			// 闭包变量（将在
			// 导入过程中重新创建）是通过一个负id给出的，从-2开始，
			// 在导出过程中用于在函数
			// 中引用它-1代表空白。
			w.dclIndex[cv] = -(i + 2) - w.maxClosureVarIndex
		}
		w.maxClosureVarIndex += len(n.Func.ClosureVars)

		// 像w.funcBody（n.Func），但不是为了。Inl 
		w.writeNames(n.Func.Dcl)
		w.stmtList(n.Func.Body)

	// case O编译：
	// 应该通过类型检查解决-默认情况下处理

	case ir.OPTRLIT:
		n := n.(*ir.AddrExpr)
		if go117ExportTypes {
			w.op(ir.OPTRLIT)
		} else {
			w.op(ir.OADDR)
		}
		w.pos(n.Pos())
		w.expr(n.X)
		if go117ExportTypes {
			w.typ(n.Type())
		}

	case ir.OSTRUCTLIT:
		n := n.(*ir.CompLitExpr)
		w.op(ir.OSTRUCTLIT)
		w.pos(n.Pos())
		w.typ(n.Type())
		w.fieldList(n.List) // 字段名的特殊处理

	case ir.OCOMPLIT, ir.OARRAYLIT, ir.OSLICELIT, ir.OMAPLIT:
		n := n.(*ir.CompLitExpr)
		if go117ExportTypes {
			w.op(n.Op())
		} else {
			w.op(ir.OCOMPLIT)
		}
		w.pos(n.Pos())
		w.typ(n.Type())
		w.exprList(n.List)
		if go117ExportTypes && n.Op() == ir.OSLICELIT {
			w.uint64(uint64(n.Len))
		}
	case ir.OKEY:
		n := n.(*ir.KeyExpr)
		w.op(ir.OKEY)
		w.pos(n.Pos())
		w.expr(n.Key)
		w.expr(n.Value)

	// case OSTRUCTKEY:
	// 无法访问-在elemList 

	case ir.OXDOT, ir.ODOT, ir.ODOTPTR, ir.ODOTINTER, ir.ODOTMETH, ir.OMETHVALUE, ir.OMETHEXPR:
		n := n.(*ir.SelectorExpr)
		if go117ExportTypes {
			// 为GO117ExportType，我们通常会看到除
			// OXDOT之外的所有操作，但我们可以看到OXDOT用于泛型函数。
			w.op(n.Op())
		} else {
			w.op(ir.OXDOT)
		}
		w.pos(n.Pos())
		w.expr(n.X)
		w.exoticSelector(n.Sel)
		if go117ExportTypes {
			w.exoticType(n.Type())
			if n.Op() == ir.OXDOT {
				// n.方法引用的选择将在导入期间被重新构造。
				w.bool(n.Selection != nil)
			} else if n.Op() == ir.ODOT || n.Op() == ir.ODOTPTR || n.Op() == ir.ODOTINTER {
				w.exoticField(n.Selection)
			}
			// n.OMETHEXPR、ODOTMETH和OMETHVALUE不需要选择。
			// 将在导入期间重建。n、 选择是在
			// transformDot（）期间为OXDOT计算的。
		}

	case ir.ODOTTYPE, ir.ODOTTYPE2:
		n := n.(*ir.TypeAssertExpr)
		if go117ExportTypes {
			w.op(n.Op())
		} else {
			w.op(ir.ODOTTYPE)
		}
		w.pos(n.Pos())
		w.expr(n.X)
		w.typ(n.Type())

	case ir.ODYNAMICDOTTYPE, ir.ODYNAMICDOTTYPE2:
		n := n.(*ir.DynamicTypeAssertExpr)
		w.op(n.Op())
		w.pos(n.Pos())
		w.expr(n.X)
		w.expr(n.T)
		w.typ(n.Type())

	case ir.OINDEX, ir.OINDEXMAP:
		n := n.(*ir.IndexExpr)
		if go117ExportTypes {
			w.op(n.Op())
		} else {
			w.op(ir.OINDEX)
		}
		w.pos(n.Pos())
		w.expr(n.X)
		w.expr(n.Index)
		if go117ExportTypes {
			w.exoticType(n.Type())
			if n.Op() == ir.OINDEXMAP {
				w.bool(n.Assigned)
			}
		}

	case ir.OSLICE, ir.OSLICESTR, ir.OSLICEARR:
		n := n.(*ir.SliceExpr)
		if go117ExportTypes {
			w.op(n.Op())
		} else {
			w.op(ir.OSLICE)
		}
		w.pos(n.Pos())
		w.expr(n.X)
		w.exprsOrNil(n.Low, n.High)
		if go117ExportTypes {
			w.typ(n.Type())
		}

	case ir.OSLICE3, ir.OSLICE3ARR:
		n := n.(*ir.SliceExpr)
		if go117ExportTypes {
			w.op(n.Op())
		} else {
			w.op(ir.OSLICE3)
		}
		w.pos(n.Pos())
		w.expr(n.X)
		w.exprsOrNil(n.Low, n.High)
		w.expr(n.Max)
		if go117ExportTypes {
			w.typ(n.Type())
		}

	case ir.OCOPY, ir.OCOMPLEX, ir.OUNSAFEADD, ir.OUNSAFESLICE:
		// 与其他内置调用（如OREAL）一样处理
		n := n.(*ir.BinaryExpr)
		w.op(n.Op())
		w.pos(n.Pos())
		w.expr(n.X)
		w.expr(n.Y)
		if go117ExportTypes {
			w.typ(n.Type())
		} else {
			w.op(ir.OEND)
		}

	case ir.OCONV, ir.OCONVIFACE, ir.OCONVIDATA, ir.OCONVNOP, ir.OBYTES2STR, ir.ORUNES2STR, ir.OSTR2BYTES, ir.OSTR2RUNES, ir.ORUNESTR, ir.OSLICE2ARRPTR:
		n := n.(*ir.ConvExpr)
		if go117ExportTypes {
			w.op(n.Op())
		} else {
			w.op(ir.OCONV)
		}
		w.pos(n.Pos())
		w.typ(n.Type())
		w.expr(n.X)

	case ir.OREAL, ir.OIMAG, ir.OCAP, ir.OCLOSE, ir.OLEN, ir.ONEW, ir.OPANIC:
		n := n.(*ir.UnaryExpr)
		w.op(n.Op())
		w.pos(n.Pos())
		w.expr(n.X)
		if go117ExportTypes {
			if n.Op() != ir.OPANIC {
				w.typ(n.Type())
			}
		} else {
			w.op(ir.OEND)
		}

	case ir.OAPPEND, ir.ODELETE, ir.ORECOVER, ir.OPRINT, ir.OPRINTN:
		n := n.(*ir.CallExpr)
		w.op(n.Op())
		w.pos(n.Pos())
		w.exprList(n.Args) // 发出终止OEND 
		// 只有append（）调用可能包含“…”参数
		if n.Op() == ir.OAPPEND {
			w.bool(n.IsDDD)
		} else if n.IsDDD {
			base.Fatalf("exporter: unexpected '...' with %v call", n.Op())
		}
		if go117ExportTypes {
			if n.Op() != ir.ODELETE && n.Op() != ir.OPRINT && n.Op() != ir.OPRINTN {
				w.typ(n.Type())
			}
		}

	case ir.OCALL, ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER, ir.OGETG:
		n := n.(*ir.CallExpr)
		if go117ExportTypes {
			w.op(n.Op())
		} else {
			w.op(ir.OCALL)
		}
		w.pos(n.Pos())
		w.stmtList(n.Init())
		w.expr(n.X)
		w.exprList(n.Args)
		w.bool(n.IsDDD)
		if go117ExportTypes {
			w.exoticType(n.Type())
		}

	case ir.OMAKEMAP, ir.OMAKECHAN, ir.OMAKESLICE:
		n := n.(*ir.MakeExpr)
		w.op(n.Op()) // 对于导入程序
		w.pos(n.Pos())
		w.typ(n.Type())
		switch {
		default:
			// 空列表
			w.op(ir.OEND)
		case n.Cap != nil:
			w.expr(n.Len)
			w.expr(n.Cap)
			w.op(ir.OEND)
		case n.Len != nil && (n.Op() == ir.OMAKESLICE || !n.Len.Type().IsUntyped()):
			// 注意：额外的条件存在，因为make（T）for 
			// T a map或chan type将一个非类型化的零添加为
			// 参数。不要在这里序列化那个论点。
			w.expr(n.Len)
			w.op(ir.OEND)
		case n.Len != nil && go117ExportTypes:
			w.expr(n.Len)
			w.op(ir.OEND)
		}

	case ir.OLINKSYMOFFSET:
		n := n.(*ir.LinksymOffsetExpr)
		w.op(ir.OLINKSYMOFFSET)
		w.pos(n.Pos())
		w.string(n.Linksym.Name)
		w.uint64(uint64(n.Offset_))
		w.typ(n.Type())

	// 一元表达式
	case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT, ir.ORECV, ir.OIDATA:
		n := n.(*ir.UnaryExpr)
		w.op(n.Op())
		w.pos(n.Pos())
		w.expr(n.X)
		if go117ExportTypes {
			w.typ(n.Type())
		}

	case ir.OADDR:
		n := n.(*ir.AddrExpr)
		w.op(n.Op())
		w.pos(n.Pos())
		w.expr(n.X)
		if go117ExportTypes {
			w.typ(n.Type())
		}

	case ir.ODEREF:
		n := n.(*ir.StarExpr)
		w.op(n.Op())
		w.pos(n.Pos())
		w.expr(n.X)
		if go117ExportTypes {
			w.typ(n.Type())
		}

	case ir.OSEND:
		n := n.(*ir.SendStmt)
		w.op(n.Op())
		w.pos(n.Pos())
		w.expr(n.Chan)
		w.expr(n.Value)

	// 二元表达式
	case ir.OADD, ir.OAND, ir.OANDNOT, ir.ODIV, ir.OEQ, ir.OGE, ir.OGT, ir.OLE, ir.OLT,
		ir.OLSH, ir.OMOD, ir.OMUL, ir.ONE, ir.OOR, ir.ORSH, ir.OSUB, ir.OXOR, ir.OEFACE:
		n := n.(*ir.BinaryExpr)
		w.op(n.Op())
		w.pos(n.Pos())
		w.expr(n.X)
		w.expr(n.Y)
		if go117ExportTypes {
			w.typ(n.Type())
		}

	case ir.OANDAND, ir.OOROR:
		n := n.(*ir.LogicalExpr)
		w.op(n.Op())
		w.pos(n.Pos())
		w.expr(n.X)
		w.expr(n.Y)
		if go117ExportTypes {
			w.typ(n.Type())
		}

	case ir.OADDSTR:
		n := n.(*ir.AddStringExpr)
		w.op(ir.OADDSTR)
		w.pos(n.Pos())
		w.exprList(n.List)
		if go117ExportTypes {
			w.typ(n.Type())
		}

	case ir.ODCLCONST:
		// 如果导出，DCLCONST应该被删除，因为它的用法
		// 已经被文本

	case ir.OFUNCINST:
		n := n.(*ir.InstExpr)
		w.op(ir.OFUNCINST)
		w.pos(n.Pos())
		w.expr(n.X)
		w.uint64(uint64(len(n.Targs)))
		for _, targ := range n.Targs {
			w.typ(targ.Type())
		}
		if go117ExportTypes {
			w.typ(n.Type())
		}

	case ir.OSELRECV2:
		n := n.(*ir.AssignListStmt)
		w.op(ir.OSELRECV2)
		w.pos(n.Pos())
		w.stmtList(n.Init())
		w.exprList(n.Lhs)
		w.exprList(n.Rhs)
		w.bool(n.Def)

	default:
		base.Fatalf("cannot export %v (%d) node\n"+
			"\t==> please file an issue and assign to gri@", n.Op(), int(n.Op()))
	}
}

func (w *exportWriter) op(op ir.Op) {
	if debug {
		w.uint64(magic)
	}
	w.uint64(uint64(op))
}

func (w *exportWriter) exprsOrNil(a, b ir.Node) {
	ab := 0
	if a != nil {
		ab |= 1
	}
	if b != nil {
		ab |= 2
	}
	w.uint64(uint64(ab))
	if ab&1 != 0 {
		w.expr(a)
	}
	if ab&2 != 0 {
		w.node(b)
	}
}

func (w *exportWriter) fieldList(list ir.Nodes) {
	w.uint64(uint64(len(list)))
	for _, n := range list {
		n := n.(*ir.StructKeyExpr)
		w.pos(n.Pos())
		w.exoticField(n.Field)
		w.expr(n.Value)
	}
}

func (w *exportWriter) localName(n *ir.Name) {
	if ir.IsBlank(n) {
		w.int64(-1)
		return
	}

	i, ok := w.dclIndex[n]
	if !ok {
		base.FatalfAt(n.Pos(), "missing from dclIndex: %+v", n)
	}
	w.int64(int64(i))
}

func (w *exportWriter) localIdent(s *types.Sym) {
	if w.currPkg == nil {
		base.Fatalf("missing currPkg")
	}

	// 匿名参数替换。
	if s == nil {
		w.string("")
		return
	}

	name := s.Name
	if name == "_" {
		w.string("_")
		return
	}

	// autotmp变量的名称并不重要；他们只需要
	// 独一无二。为了稳定出口数据，只需将“$”写为
	// 一个标记，然后让进口商生成自己的唯一名称。
	if strings.HasPrefix(name, ".autotmp_") {
		w.string("$autotmp")
		return
	}

	if i := strings.LastIndex(name, "."); i >= 0 && !strings.HasPrefix(name, LocalDictName) {
		base.Fatalf("unexpected dot in identifier: %v", name)
	}

	if s.Pkg != w.currPkg {
		base.Fatalf("weird package in name: %v => %v from %q, not %q", s, name, s.Pkg.Path, w.currPkg.Path)
	}

	w.string(name)
}

type intWriter struct {
	bytes.Buffer
}

func (w *intWriter) int64(x int64) {
	var buf [binary.MaxVarintLen64]byte
	n := binary.PutVarint(buf[:], x)
	w.Write(buf[:n])
}

func (w *intWriter) uint64(x uint64) {
	var buf [binary.MaxVarintLen64]byte
	n := binary.PutUvarint(buf[:], x)
	w.Write(buf[:n])
}

// 如果go117ExportTypes为真，那么我们在导出函数体时会写入类型信息，所以这些函数体在导入时不需要重新进行类型检查。
// 此标志将一些其他信息添加到序列化流以及之前在类型检查期间重新计算的
// 中，例如
// 专用操作码（例如从OXDOT到ODOTPTR）和辅助
// 信息（例如OSLICELIT的长度字段）。
const go117ExportTypes = true
const Go117ExportTypes = go117ExportTypes

// 用于字典参数或局部变量的名称。
const LocalDictName = ".dict"
